using System;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Text;
using System.Threading;


namespace network
{
    public class UDPclient
    {
        private static int serverPort = 3001;
        private UdpClient sender;
        private IPAddress bindAddress;

        private ASCIIEncoding encoder;

        public UDPclient(IPAddress interfaceIp)
        {
            this.encoder = new ASCIIEncoding();
            this.bindAddress = getInterface(interfaceIp);
            //this.sender = new UdpClient(new IPEndPoint(getNetworkInterface(), 0));
        }

        public UDPclient(string NIC)
        {
            this.encoder = new ASCIIEncoding();
            this.bindAddress = getInterface(IPAddress.Parse(NIC));
        }

        private IPAddress getInterface(IPAddress ip)
        {
            NetworkInterface[] interfaces = NetworkInterface.GetAllNetworkInterfaces();
            foreach (NetworkInterface ni in interfaces)
            {
                UnicastIPAddressInformationCollection ipColection = ni.GetIPProperties().UnicastAddresses;
                foreach (UnicastIPAddressInformation ipInfo in ipColection)
                {
					IPAddress address;
					try {
						address = ipInfo.IPv4Mask;
					} catch (NotImplementedException ex) {
						address = IPAddress.Parse("255.255.255.0");
					}
					
                    if ((ipInfo.Address.AddressFamily == AddressFamily.InterNetwork))
                    {
                        bool equals = true;
                        byte[] ip1 = ipInfo.Address.GetAddressBytes();
                        byte[] ip2 = ip.GetAddressBytes();
                        byte[] mask = address.GetAddressBytes();

                        for (int i = 0; i < mask.Length; i++)
                        {
                            ip1[i] = (byte)(ip1[i] & mask[i]);
                            ip2[i] = (byte)(ip2[i] & mask[i]);
                            if (ip1[i] != ip2[i])
                            {
                                equals = false;
                            }
                        }
                        if (equals)
                        {
                            return ipInfo.Address;
                        }
                    }
                    else if (ipInfo.Address.AddressFamily == AddressFamily.InterNetworkV6)
                    {
                        // no support for IPv6 yet
                    }
                }
            }
            throw new NoInterface();
        }


        public void sendMsg(string msg, Node node)
        {
            this.sendMsg(msg, node.endPoint);
        }

        public void sendMsg(string msg) // Broadcast
        {
            byte[] outBuffer = encoder.GetBytes(msg);

            Thread feedback;
            FeedbackData fbData = new FeedbackData(new IPEndPoint(IPAddress.Any, 0));

            this.sender = new UdpClient(new IPEndPoint(this.bindAddress, 0));
            this.sender.Send(outBuffer, outBuffer.Length, new IPEndPoint(IPAddress.Broadcast, serverPort)); //sends from a random port to 3001

            feedback = new Thread(new ParameterizedThreadStart(listenFeedback));
            feedback.Start(fbData);  // modifies fbData.rcvData

            Thread.Sleep(3000); //waits for a feedback timeout
            if (fbData.rcvData == null)
            {
                feedback.Abort();
                throw new AckTimeout(); //receives at the same random port from 3001
            }
            else if (!GenericMethods.equalMsg("ACK", fbData.rcvData))
            {
                feedback.Abort();
                throw new UnexpectedMsg(fbData.rcvData); //receives at the same random port from 3001
            }
        }

        public void sendMsg(string msg, IPEndPoint remoteEP)
        {
            byte[] outBuffer = encoder.GetBytes(msg);
            Thread feedback;
            FeedbackData fbData = new FeedbackData(remoteEP);

            this.sender = new UdpClient(new IPEndPoint(this.bindAddress, 0));
            this.sender.Send(outBuffer, outBuffer.Length, new IPEndPoint(remoteEP.Address, serverPort)); //sends from a random port to 3001

            feedback = new Thread(new ParameterizedThreadStart(listenFeedback));
            feedback.Start(fbData);  // modifies fbData.rcvData

            Thread.Sleep(2000); //waits for a feedback timeout
            if (fbData.rcvData == null)
            {
                feedback.Abort();
                throw new AckTimeout(); //receives at the same random port from 3001
            }
            else if (!GenericMethods.equalMsg("ACK", fbData.rcvData))
            {
                feedback.Abort();
                throw new UnexpectedMsg(fbData.rcvData); //receives at the same random port from 3001
            }
        }

        private void listenFeedback(object data)
        {
            try
            {
                ((FeedbackData)data).rcvData = this.sender.Receive(ref ((FeedbackData)data).ep);
            }
            catch (SocketException) // wtf?? is udp suposed to do this?
            {
                ((FeedbackData)data).rcvData = null;
            }
        }
    }



    public class UDPserver
    {
        // to signal listen thread that it needs stop
        private volatile bool listening;

        private static int listeningPort = 3001;
        private ASCIIEncoding encoder;

        private IPEndPoint remoteEP;
        private UdpClient receiver;

        public delegate void CallHandler(UDPserver server, MessageArgs args);
        public event CallHandler callHandler;

        public UDPserver()
        {
            this.encoder = new ASCIIEncoding();

            this.remoteEP = new IPEndPoint(IPAddress.Any, 0);
            this.receiver = new UdpClient(listeningPort);
            this.receiver.DontFragment = true;
            this.listening = true;

            Thread listenThread = new Thread(new ThreadStart(listenUDP));
            listenThread.Start();
        }

        public void stop()
        {
            this.listening = false;
        }

        private void listenUDP()
        {
            byte[] rcvData = new byte[1024];
            byte[] outBuffer = encoder.GetBytes("ACK");

            while (this.listening == true)
            {
                rcvData = receiver.Receive(ref this.remoteEP); //receives at port 3001

                // ack
                receiver.Send(outBuffer, outBuffer.Length, remoteEP); // sends from port 3001 to the same port the client used to send its message

                //do something
                if (callHandler != null)
                {
                    callHandler.BeginInvoke(this, new MessageArgs(rcvData), null, null); //assyncronous
                }
                //log("Msg: \"" + encoder.GetString(rcvData) + String.Format("Came from {0}", remoteEP));
            }
        }
    }
}
